Skip to content

feat: probe Sui providers by genesis checkpoint digest - #4070

Open
haiyuechen-nearone wants to merge 9 commits into
4003-probe-aptos-chain-idfrom
4003-probe-sui-genesis-digest
Open

feat: probe Sui providers by genesis checkpoint digest#4070
haiyuechen-nearone wants to merge 9 commits into
4003-probe-aptos-chain-idfrom
4003-probe-sui-genesis-digest

Conversation

@haiyuechen-nearone

@haiyuechen-nearone haiyuechen-nearone commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #4093.

Notes for review

  • Nothing is normalized. Base58 is case sensitive and carries no prefix or padding.

  • NotFound reads off the response type, through the HasAbsenceMeaning traits feat: probe Aptos providers by ledger chain id #4069 added

  • DeadlineExceeded maps to Timeout instead of RpcRequestFailed, so a slow provider reports as timed out rather than unreachable. Both are transient, so retries and the signing path fan out are unchanged.

@haiyuechen-nearone haiyuechen-nearone changed the title feat(probe): probe Sui for its genesis checkpoint digest feat: probe Sui for its genesis checkpoint digest Aug 5, 2026
@haiyuechen-nearone haiyuechen-nearone changed the title feat: probe Sui for its genesis checkpoint digest feat(probe): identify Sui by its genesis checkpoint digest Aug 7, 2026
@haiyuechen-nearone
haiyuechen-nearone force-pushed the 4003-probe-sui-genesis-digest branch from 8141a21 to 052f530 Compare August 7, 2026 14:35
@haiyuechen-nearone
haiyuechen-nearone force-pushed the 4003-probe-sui-genesis-digest branch from 052f530 to fa6f569 Compare August 7, 2026 19:46
@haiyuechen-nearone haiyuechen-nearone changed the title feat(probe): identify Sui by its genesis checkpoint digest feat: probe Sui providers by genesis checkpoint digest Aug 11, 2026
@haiyuechen-nearone
haiyuechen-nearone marked this pull request as ready for review August 11, 2026 11:13
@haiyuechen-nearone
haiyuechen-nearone force-pushed the 4003-probe-sui-genesis-digest branch from 5aa7e47 to 5ae8dd8 Compare August 11, 2026 11:13
@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Pull request overview

Wires Sui into the network-fingerprint probe by implementing NetworkFingerprintInspector for SuiInspector, reading the base58 genesis-checkpoint digest out of GetServiceInfo.chain_id. The old classify_status free function is replaced by the ClassifyRpcOutcome blanket impl over Result<T, tonic::Status> introduced in #4069, so NotFound now resolves through the response type's HasAbsenceMeaning (transaction -> TransactionNotFound, service info -> RpcRequestRejected) rather than being unconditionally a missing transaction. DeadlineExceeded is split out to Timeout, matching what the Aptos classifier already does.

The probe path is still library-only (probe_all_providers has no production caller yet, see crates/foreign-chain-health-check/src/lib.rs:43), and Timeout/RpcRequestFailed are indistinguishable to FanOut::extract (both are is_transient()), so the signing path is unaffected - as the PR body claims.

Changes:

  • SuiInspector gains network_fingerprint (identity canonical_fingerprint, since base58 has one spelling) and a ForeignChain::Sui arm in probe_all_providers; the TODO(#4003) is retired.
  • classify_status -> ClassifyRpcOutcome for Result<T, Status>, with HasAbsenceMeaning for GetTransactionResponse / GetServiceInfoResponse, and DeadlineExceeded remapped to Timeout.
  • Test scaffolding: a real tonic gRPC fake (FakeSuiLedger/FakeSuiServer) in the health-check crate, since httpmock cannot serve gRPC; MockSuiClient now arms both RPCs; a live #[ignore]d fingerprint check mirroring the Aptos one.
  • Docs: Sui added to the probe table, prose rewritten around "every chain with an inspector is probed".

Reviewed changes

Per-file summary
File Description
crates/foreign-chain-inspector/src/sui/inspector.rs NetworkFingerprintInspector impl; classify_status replaced by ClassifyRpcOutcome; DeadlineExceeded -> Timeout; unit tests renamed/extended
crates/foreign-chain-health-check/src/probe.rs ForeignChain::Sui probe arm, timeout_of reuse, TODO(#4003) removed, tonic-based fake ledger + 3 probe tests
crates/foreign-chain-inspector/tests/sui_inspector.rs MockSuiClient arms get_service_info; 3 network_fingerprint tests
crates/foreign-chain-inspector/tests/sui_rpc_manual.rs #[ignore]d live fingerprint check against the mainnet archive
crates/foreign-chain-health-check/Cargo.toml, Cargo.lock tonic dev-dependency with router/server for the fake gRPC server
docs/foreign-chain-transactions.md Sui row in the probe table; prose on which chains are probed

Findings

Blocking (must fix before merge):

  • docs/foreign-chain-transactions.md:551 and :722-723 - both new sentences name ton as a chain that "has no inspector, so [it] ignore[s] expected_network_fingerprint" / "report[s] ProbeNotImplemented whether the field is set or not". ton has no slot in the node's config at all (crates/node-config/src/foreign_chains.rs:17-43) and is never yielded by all_configured_chains (:164-182), so it can never be configured, probed, or produce a report row - the ProbeNotImplemented claim is false for it, and it contradicts the unchanged sentence at :713 ("solana and ethereum are configurable but absent from the table"). Same inaccuracy in the code comment at crates/foreign-chain-health-check/src/probe.rs:140. Either drop ton from all three spots, or state that it is a contract-side chain with no node config.

Non-blocking (nits, follow-ups, suggestions):

  • crates/foreign-chain-health-check/src/probe.rs:141 - with Sui landed, the only variants reaching _ => ProbeNotImplemented are Solana and Ethereum, yet the docs now promise "every chain with an inspector is probed". The wildcard silently breaks that promise for the next chain added to ForeignChain. Spelling the arm out (ForeignChain::Solana | ForeignChain::Ethereum | ForeignChain::Ton =>) turns that into a compile error instead of an unprobed row on the dashboard.
  • crates/foreign-chain-inspector/src/sui/inspector.rs:137 - the impl is keyed on tonic::Status, not on anything Sui-specific, but lives in the Sui module. A future gRPC-based chain silently inherits Sui's mapping and, by coherence, cannot override it. Consider moving it beside the trait in lib.rs so its reach is visible at the definition site.
  • crates/foreign-chain-inspector/src/sui/inspector.rs:148-151 - worth confirming (I could not check tonic's source from this checkout): tonic derives a Status from transport-level std::io::Errors, and if that conversion maps ErrorKind::NotFound to Code::NotFound, a purely local transport failure would be classified as TransactionNotFound - a non-transient chain verdict in FanOut::extract, not an availability blip. Pre-existing rather than introduced here, but this PR is the one refining exactly this axis, so it seems the right place to confirm the assumption that Code::NotFound only ever comes from the server.
  • crates/foreign-chain-inspector/tests/sui_inspector.rs:30 and :43 - the un-armed RPC now answers Status::unimplemented(...), which the code under test classifies as RpcRequestRejected. A test that reaches the wrong RPC therefore fails (or passes) on a plausible-looking assertion instead of panicking loudly; get_checkpoint at :59 still uses unimplemented!(), so the mock now mixes both conventions. Option<Result<...>> plus .expect("test did not arm get_service_info") would keep the loud failure.
  • crates/foreign-chain-inspector/tests/sui_inspector.rs:20 - "answering the one call a test arms, and refusing the other" does not describe status(), which arms both with the same Status.

⚠️ Issues found

@haiyuechen-nearone
haiyuechen-nearone force-pushed the 4003-probe-sui-genesis-digest branch from f2f2d16 to 90a2d58 Compare August 11, 2026 16:23
@haiyuechen-nearone haiyuechen-nearone self-assigned this Aug 11, 2026
@haiyuechen-nearone

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Pull request overview

Third step of the #4003 probe stack: SuiInspector gains NetworkFingerprintInspector, reading the base58 genesis-checkpoint digest out of GetServiceInfo.chain_id, and probe_all_providers gains a ForeignChain::Sui arm. The old classify_status free function is replaced by the ClassifyRpcOutcome blanket impl over Result<T, tonic::Status> introduced in #4069, so NotFound now resolves through the response type's HasAbsenceMeaning (transaction → TransactionNotFound, service info → RpcRequestRejected) rather than being unconditionally a missing transaction. DeadlineExceeded is split out to Timeout.

The blocking documentation inaccuracy from the previous round is fixed: the prose and the code comment now name only solana and ethereum, which matches both all_configured_chains (crates/node-config/src/foreign_chains.rs:164-182) and the set of inspector modules that actually exist. MockSuiClient now panics on an unarmed RPC instead of answering a plausible-looking Status::unimplemented, and its doc comment describes what it does.

probe_all_providers still has no production caller (crates/foreign-chain-health-check/src/lib.rs:43), and Timeout/RpcRequestFailed are indistinguishable to FanOut::extract (both is_transient()), so the signing path is unaffected — as the PR body claims.

Changes:

  • NetworkFingerprintInspector for SuiInspector, with an identity canonical_fingerprint (base58 has one spelling), plus the ForeignChain::Sui arm in probe_all_providers; TODO(#4003) retired.
  • classify_statusimpl<T: HasAbsenceMeaning> ClassifyRpcOutcome for Result<T, Status>, with HasAbsenceMeaning for GetTransactionResponse / GetServiceInfoResponse, and DeadlineExceeded remapped to Timeout.
  • Test scaffolding: a real tonic gRPC fake (FakeSuiLedger/FakeSuiServer) in the health-check crate, since httpmock cannot serve gRPC; six probe_all_providers verdict tests; MockSuiClient arms each RPC independently; a live #[ignore]d fingerprint check mirroring the Aptos one.
  • Docs: Sui row in the probe table, prose rewritten around "every chain with an inspector is probed".

Reviewed changes

Per-file summary
File Description
crates/foreign-chain-inspector/src/sui/inspector.rs NetworkFingerprintInspector impl; classify_statusClassifyRpcOutcome; HasAbsenceMeaning for the two response types; DeadlineExceededTimeout; unit tests renamed/extended
crates/foreign-chain-health-check/src/probe.rs ForeignChain::Sui probe arm, timeout_of reuse, TODO(#4003) removed, tonic-based fake ledger + 6 probe tests
crates/foreign-chain-inspector/tests/sui_inspector.rs MockSuiClient arms each RPC separately and panics when a test reaches an unarmed one; 3 network_fingerprint tests
crates/foreign-chain-inspector/tests/sui_rpc_manual.rs #[ignore]d live fingerprint check against the mainnet archive
crates/foreign-chain-health-check/Cargo.toml, Cargo.lock tonic dev-dependency with router/server for the fake gRPC server
docs/foreign-chain-transactions.md Sui row in the probe table; prose on which chains are probed

Findings

Non-blocking (nits, follow-ups, suggestions):

  • crates/foreign-chain-inspector/src/sui/inspector.rs:157 / crates/foreign-chain-health-check/src/probe.rs:142 — the case this arm exists to serve (a provider that stalls) is decided by a race between three deadlines set to the same value. prepare_sui(provider, timeout) gives the client a per-request gRPC deadline (request.set_timeout, crates/foreign-chain-rpc-interfaces/src/sui.rs:95-99) equal to timeout_of(chain_config), which is the same duration FanOut::network_fingerprints passes to its own tokio::time::timeout (crates/foreign-chain-inspector/src/lib.rs:253). So a stall can surface as: the server honouring grpc-timeoutDEADLINE_EXCEEDEDTimedOut; tonic's own client-side timeout → TimedOut only if tonic reports it as DEADLINE_EXCEEDED; or the probe's tokio::time::timeoutTimeoutTimedOut. I could not check tonic's source from this checkout, but its GrpcTimeout layer has historically produced Status::cancelled("Timeout expired"), which lands in the RpcRequestFailed arm → ProviderStatus::Unreachable — exactly the verdict the change sets out to remove, and what the comment on :156 promises it will not be. Worth confirming; if it is Cancelled, the cheapest fix is to let the probe own the deadline (pass a generous or no per-request timeout to prepare_sui from the probe arm) rather than to also map Cancelled, which legitimately means "caller cancelled" in other contexts.
    • Related coverage gap: probe_all_providers__should_report_a_slow_sui_provider_as_timed_out pins the mapping — the fake answers DEADLINE_EXCEEDED immediately — not the scenario the name describes, and the stalled-call test was dropped in 63c9fc6. Starknet still has the end-to-end version (probe_all_providers__should_report_a_provider_that_does_not_answer_in_time, probe.rs:599); Sui now has nothing equivalent.
  • crates/foreign-chain-health-check/src/probe.rs:995FakeSuiLedger implements only get_service_info, so the remaining LedgerService RPCs necessarily fall through to tonic's generated default stubs and answer Status::unimplemented. Through classified() that is RpcRequestRejectedProviderStatus::RequestRejected: a plausible-looking verdict, which is the same soft failure f3ae667 just removed from MockSuiClient. If the probe ever calls a second RPC, these tests assert a wrong status instead of failing loudly. Overriding the RPCs the fake should never receive with unreachable!() keeps the two fakes consistent.
  • Unchanged from the previous round, restated only so they are not lost: the _ => wildcard at probe.rs:147 (now that the docs promise "every chain with an inspector is probed", spelling out ForeignChain::Solana | ForeignChain::Ethereum turns the next chain added to ForeignChain into a compile error rather than a silently unprobed dashboard row); the ClassifyRpcOutcome for Result<T, Status> impl living in the Sui module while being keyed only on tonic::Status; and confirming that Code::NotFound can only originate server-side, never from a local transport error.

✅ Approved

@haiyuechen-nearone
haiyuechen-nearone force-pushed the 4003-probe-sui-genesis-digest branch from f3ae667 to 39b7ec2 Compare August 13, 2026 19:45
`GetServiceInfo` reports the digest as base58, which is the form it is
published and configured in, so nothing is normalized. Completes the probe for
every chain that has an inspector.
Serve a fake `LedgerService` over gRPC so the probe tests cover Sui end to
end: on its genesis digest, on another network, unreachable, and stalled. The
mock HTTP server the other chains use cannot answer a gRPC call.

Read the Sui `NotFound` meaning off the response type through
`ClassifyRpcOutcome`, as Aptos already does, rather than overriding it at the
`network_fingerprint` call site, so a further call site cannot silently
inherit `TransactionNotFound`. Derive the attempt deadline once through
`timeout_of`, and move the client deadline test beside `prepare_sui`.
Every caller bounds the call itself: `network_fingerprints` arms a deadline
before tonic stamps the client's, and the signing flow wraps `extract` in
`FOREIGN_CHAIN_INSPECTION_TIMEOUT`. The client's `DeadlineExceeded` never
surfaces, so the test pinned a tonic detail no caller observes.

A provider that answers `DEADLINE_EXCEEDED` itself does reach the mapping, and
`classified__should_name_a_deadline_as_a_timeout` covers that without a server.
The fake ledger loses the delay it only needed in order to stall.
The fake ledger now serves whatever answer a test arms, so one server reaches
every verdict: on chain, on another network, service info without a chain id,
a refused service, and a deadline. Each case enters through
`probe_all_providers`, as the other chains' cases do, which also pins the
dispatch arm rather than leaving that to the closed port case alone.
A mock that answered every RPC with a plausible status let a test that
called the wrong one fail on its assertion instead of on the wrong call.
Also drops Ton from the chains said to have no inspector: it has no
config section, so it never reaches the probe.
Move the NotFound rationale down to the arm it explains, and drop the comments that restate the code or the fixture they sit on.
@haiyuechen-nearone
haiyuechen-nearone force-pushed the 4003-probe-sui-genesis-digest branch from 3a4ecd9 to 888d013 Compare August 14, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Probe Sui for its genesis checkpoint digest

1 participant